﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using RenRen.Schema;
using RenRen.Session;
using RenRen.Utility;

namespace RenRen.Rest
{
    /// <summary>
    /// 人人网REST调用实现
    /// </summary>
    public class RestBase
    {
        #region 私有成员
        private static Dictionary<string, Enums.FileType> _mimeTypes = new Dictionary<string, Enums.FileType> {
            {"video/x-ms-asf", Enums.FileType.asf},
            {"video/avi", Enums.FileType.avi},
            {"video/x-flv", Enums.FileType.flv},
            {"video/x-matroska", Enums.FileType.mkv},
            {"video/quicktime",Enums.FileType.mov},
            {"video/mp4",Enums.FileType.mp4},
            {"video/mpeg",Enums.FileType.mpeg},
            {"application/x-winamp",Enums.FileType.nsv},
            {"video/ogg",Enums.FileType.ogm},
            {"video/dvd",Enums.FileType.vob},
            {"video/x-ms-wmv",Enums.FileType.wmv},
            {"image/gif",Enums.FileType.gif},
            {"image/jpeg",Enums.FileType.jpg},
            {"image/png",Enums.FileType.png},
            {"image/psd",Enums.FileType.psd},
            {"image/tiff",Enums.FileType.tiff},
            {"image/jp2",Enums.FileType.jp2},
            {"image/iff",Enums.FileType.iff},
            {"image/vnd.wap.wbmp",Enums.FileType.wbmp},
            {"image/x-xbitmap",Enums.FileType.xbm},
            {"image/bmp",Enums.FileType.bmp},
            };
        #endregion
        #region Public Properties

        ///<summary>
        /// 批处理对象
        ///</summary>
        public Batch Batch { get; internal set; }

        #endregion Public Properties

        #region Protected Properties

        /// <summary>
        /// 会话对象
        /// </summary>
        public RenRenSession Session { get; internal set; }
        protected bool HasSession { get { return !string.IsNullOrEmpty(Session.SessionKey); } }
        #endregion Protected Properties

        #region Methods

        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="session">Session object</param>
        public RestBase(RenRenSession session)
        {
            Session = session;
        }

        #endregion Constructor

        #region Public Methods

        #region Synchronous Methods

        /// <summary>
        /// 通过REST Api给人人发送请求. 所有其它方法都通过这个方法和人人网通信.
        /// 未实现的方法也可以通过本API直接调用
        /// </summary>
        /// <param name="parameterDictionary">
        /// 调用api的参数字典. 确保提供了人人网所需求的参数。
        /// 本框架会自动提取api_key, call_id, sig, v, 和 session_key 这些参数.
        /// </param>
        /// <returns>XML</returns>
        public string SendRequest(IDictionary<string, string> parameterDictionary)
        {
            return SendRequest(parameterDictionary, true);
        }
        
        /// <summary>
        /// 通过REST Api给人人发送请求. 所有其它方法都通过这个方法和人人网通信.
        /// 未实现的方法也可以通过本API直接调用
        /// </summary>
        /// <param name="parameterDictionary">
        /// 调用api的参数字典. 确保提供了人人网所需求的参数。
        /// 本框架会自动提取api_key, call_id, sig, v, 和 session_key 这些参数.
        /// </param>
        /// <param name="useSession">是否使用session.</param>
        /// <returns>XML</returns>
        public string SendRequest(IDictionary<string, string> parameterDictionary, bool useSession)
        {
            var result = SendRequestSynchronous(parameterDictionary, useSession);
            return string.IsNullOrEmpty(result) ? null : result;
        }

		/// <summary>
		/// 提交一个异步请求
		/// </summary>
		/// <typeparam name="T">输出类型</typeparam>
		/// <param name="parameterDictionary">参数字典</param>
		/// <returns>返回 (of type T) 对象.</returns>
		public T SendRequest<T>(IDictionary<string, string> parameterDictionary)
		{
			var result = SendRequestSynchronous(parameterDictionary, true);
			return Utilities.DeserializeXML<T>(result);
		}

		/// <summary>
        /// 提交一个异步请求
		/// </summary>
        /// <typeparam name="T">输出类型</typeparam>
        /// <param name="parameterDictionary">参数字典</param>
        /// <returns>返回 (of type T) 对象.</returns>
        public T SendRequest<T>(IDictionary<string, string> parameterDictionary, bool useSession)
		{
			var result = SendRequestSynchronous(parameterDictionary, useSession);
			return Utilities.DeserializeXML<T>(result);
        }

        #endregion Synchronous Methods

        #region Asynchronous Methods

        /// <summary>
		/// 发出一个到人人网服务器的请求 
		/// </summary>
		/// <typeparam name="T">返回结果类型</typeparam>
        /// <param name="parameterList">参数字典</param>
		/// <param name="callback">回调.</param>
		/// <param name="state">状态</param>
		public void SendRequestAsync<T>(Dictionary<string, string> parameterList, RenRenCallCompleted<T> callback, Object state)
		{
			SendRequestAsync(parameterList, true, callback, state);
		}

		/// <summary>
        /// 发出一个到人人网服务器的请求 
		/// </summary>
        /// <typeparam name="T">返回结果类型</typeparam>
        /// <param name="parameterList">参数字典</param>
        /// <param name="useSession">是否使用人人会话对象</param>
        /// <param name="callback">回调</param>
        /// <param name="state">状态</param>
		public void SendRequestAsync<T>(Dictionary<string, string> parameterList, bool useSession, RenRenCallCompleted<T> callback, Object state)
		{
			AsyncResult result = new AsyncResult(OnRenRenCallCompleted<T>, callback, state);
			SendRequestAsync(parameterList, useSession, result);
		}

        /// <summary>
        /// 发出一个到人人网服务器的请求
        /// </summary>
        /// <param name="parameterList">参数字典</param>
        /// <param name="callback">回调</param>
        /// <param name="state">状态</param>
        public void SendRequestAsync<TObject, TResult>(Dictionary<string, string> parameterList, RenRenCallCompleted<TResult> callback, Object state)
		{
            SendRequestAsync<TObject, TResult>(parameterList, true, callback, state);
		}

        /// <summary>
        /// 发出一个到人人网服务器的请求
        /// </summary>
        /// <param name="parameterList">参数字典</param>
        /// <param name="callback">回调</param>
        /// <param name="state">状态</param>
		public void SendRequestAsync<TObject, TResult>(Dictionary<string, string> parameterList, bool useSession, RenRenCallCompleted<TResult> callback, Object state)
		{
			SendRequestAsync<TObject, TResult>(parameterList, useSession, callback, state, "TypedValue");
		}

        /// <summary>
        /// 发出一个到人人网服务器的请求
        /// </summary>
        /// <param name="parameterList">参数字典</param>
        /// <param name="callback">回调</param>
        /// <param name="state">状态</param>
        public void SendRequestAsync<TObject, TResult>(Dictionary<string, string> parameterList, RenRenCallCompleted<TResult> callback, Object state, string propertyName)
		{
			SendRequestAsync<TObject, TResult>(parameterList, true, callback, state, propertyName);
		}

        /// <summary>
        /// 发出一个到人人网服务器的请求
        /// </summary>
        /// <param name="parameterList">参数字典</param>
        /// <param name="callback">回调</param>
        /// <param name="state">状态</param>
		public void SendRequestAsync<TObject, TResult>(Dictionary<string, string> parameterList, bool useSession, RenRenCallCompleted<TResult> callback, Object state, string propertyName)
		{
			SendRequestAsync(parameterList, useSession, new AsyncResult(ar => OnRenRenCallCompleted<TObject, TResult>(ar, propertyName), callback, state));
		}

		/// <summary>
		/// 发出一个REST调用
		/// </summary>
        /// <param name="parameterList">参数字典</param>
		/// <param name="ar"></param>
		public void SendRequestAsync(IDictionary<string, string> parameterList, AsyncResult ar)
		{
			SendRequestAsync(parameterList, true, ar);
        }

        /// <summary>
        /// 发出一个REST调用
        /// </summary>
        /// <param name="parameterList">参数字典</param>
        /// <param name="ar"></param>
		public void SendRequestAsync(IDictionary<string, string> parameterList, bool useSession, AsyncResult ar)
		{
			if (useSession && Session.SessionKey != null)
			{
				parameterList.Add("session_key", Session.SessionKey);
			}

			string postData = CreatePostData(parameterList);

			if (Batch != null && Batch.IsActive)
			{
				Batch.CallListAsync.Add(new BatchRecord(ar, postData));
			}
			else
			{
				WebClientHelper client = new WebClientHelper(ar);
				UriBuilder uriBd = new UriBuilder(Constants.RenRenRESTUrl);

				client.Method = "POST";
				client.RequestCompleted += OnRequestCompleted;
				client.SendRequest(uriBd.Uri, postData);
			}
		}

        #endregion Asynchronous Methods

        #endregion Public Methods

        #region Internal Methods

    
  
 

        internal static WebResponse postRequest(string requestUrl, string postString, bool compressHttp)
        {
            var webRequest = WebRequest.Create(requestUrl);
            webRequest.Method = "POST";
            webRequest.ContentType = "application/x-www-form-urlencoded";
            if (compressHttp)
            {
                webRequest.Headers.Add("Accept-Encoding", "gzip, deflate");                
            }
            if (!String.IsNullOrEmpty(postString))
            {
                var parameterString = Encoding.UTF8.GetBytes(postString);
                webRequest.ContentLength = parameterString.Length;

                using (var buffer = webRequest.GetRequestStream())
                {
                    buffer.Write(parameterString, 0, parameterString.Length);
                    buffer.Close();
                }
            }

            return webRequest.GetResponse();
 
        }

        internal static string processResponse(WebResponse webResponse)
        {
            string xmlResponse;

            using (var streamReader = new StreamReader(webResponse.GetResponseStream()))
            {
                xmlResponse = streamReader.ReadToEnd();
            }

            Utilities.ParseException(xmlResponse, false);

            return xmlResponse;
        }
 
        // Gaetano 'FiR3N3T' Padalino Optimization - gaetano.padalino@gmail.com
        internal static byte[] DecompressGzip(System.IO.Stream streamInput)
        {
            using (System.IO.Stream streamOutput = new System.IO.MemoryStream())
            {
                int iOutputLength = 0;

                byte[] readBuffer = new byte[4096];

                using (System.IO.Compression.GZipStream streamGZip = new System.IO.Compression.GZipStream(streamInput, System.IO.Compression.CompressionMode.Decompress))
                {
                    int i;
                    while ((i = streamGZip.Read(readBuffer, 0, readBuffer.Length)) != 0)
                    {
                        streamOutput.Write(readBuffer, 0, i);
                        iOutputLength = iOutputLength + i;
                    }
                }

                byte[] buffer = new byte[iOutputLength];
                streamOutput.Position = 0;
                streamOutput.Read(buffer, 0, buffer.Length);
                return buffer;
            }
        }

        // Gaetano 'FiR3N3T' Padalino Optimization - gaetano.padalino@gmail.com
        internal static byte[] DecompressDeflate(System.IO.Stream streamInput)
        {
            using (System.IO.Stream streamOutput = new System.IO.MemoryStream())
            {
                int iOutputLength = 0;

                byte[] readBuffer = new byte[4096];

                using (System.IO.Compression.DeflateStream streamDeflate = new System.IO.Compression.DeflateStream(streamInput, System.IO.Compression.CompressionMode.Decompress))
                {
                    int i;
                    while ((i = streamDeflate.Read(readBuffer, 0, readBuffer.Length)) != 0)
                    {
                        streamOutput.Write(readBuffer, 0, i);
                        iOutputLength = iOutputLength + i;
                    }
                }

                byte[] buffer = new byte[iOutputLength];
                streamOutput.Position = 0;
                streamOutput.Read(buffer, 0, buffer.Length);
                return buffer;
            }
        }

        // Gaetano 'FiR3N3T' Padalino Optimization - gaetano.padalino@gmail.com
        internal static string processCompressedResponse(WebResponse webResponse)
        {
            string xmlResponse = string.Empty;
            string sResponseHeader = webResponse.Headers["Content-Encoding"];

            if (!string.IsNullOrEmpty(sResponseHeader))
            {
                using (System.IO.StreamReader webReader = new System.IO.StreamReader(webResponse.GetResponseStream()))
                {
                    if (sResponseHeader.ToLower().Contains("gzip"))
                    {
                        byte[] b = DecompressGzip(webReader.BaseStream);
                        xmlResponse = Encoding.ASCII.GetString(b);
                    }
                    else if (sResponseHeader.ToLower().Contains("deflate"))
                    {
                        byte[] b = DecompressDeflate(webReader.BaseStream);
                        xmlResponse = Encoding.ASCII.GetString(b);
                    }
                }
            }
            else
            {
                using (var streamReader = new StreamReader(webResponse.GetResponseStream()))
                {
                    xmlResponse = streamReader.ReadToEnd();
                }

            }

            Utilities.ParseException(xmlResponse, false);

            return xmlResponse;
        }
 
        /// <summary>
        /// This method generates the signature based on parameters supplied
        /// </summary>
        /// <param name="parameters">List of paramenters</param>
        /// <returns>Generated signature</returns>
        internal string GenerateSignature(IDictionary<string, string> parameters)
        {
            return GenerateSignature(false, parameters);
        }

        /// <summary>
        /// This method generates the signature based on parameters supplied
        /// </summary>
        /// <param name="forceApplicationSecret">Flag to force use of Application, not User Session secret.</param>
        /// <param name="parameters">List of paramenters</param>
        /// <returns>Generated signature</returns>
        internal string GenerateSignature(bool forceApplicationSecret, IDictionary<string, string> parameters)
        {
            var signatureBuilder = new StringBuilder();

            // Sort the keys of the method call in alphabetical order
            var keyList = Utilities.ParameterDictionaryToList(parameters);
            keyList.Sort();

            // Append all the parameters to the signature input paramaters
            foreach (string key in keyList)
                signatureBuilder.Append(String.Format(CultureInfo.InvariantCulture, "{0}={1}", key, parameters[key]));

            // Append the secret to the signature builder
            signatureBuilder.Append(forceApplicationSecret ? Session.ApplicationSecret : Session.Secret);

            // Compute the MD5 hash of the signature builder
            byte[] hash = MD5Core.GetHash(signatureBuilder.ToString().Trim());

            //var md5 = MD5.Create();
            // Compute the MD5 hash of the signature builder
            //byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(signatureBuilder.ToString().Trim()));

            // Reinitialize the signature builder to store the actual signature
            signatureBuilder = new StringBuilder();

            // Append the hash to the signature
            foreach (var hashByte in hash)
                signatureBuilder.Append(hashByte.ToString("x2", CultureInfo.InvariantCulture));

            return signatureBuilder.ToString();
        }

        internal string CreateHTTPParameterList(IDictionary<string, string> parameterList)
        {
            var queryBuilder = new StringBuilder();

            parameterList.Add("api_key", Session.ApplicationKey);
            parameterList.Add("v", Constants.VERSION);
            parameterList.Add("call_id", DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture));

            if (Session.SessionSecret != null)
            {
                parameterList.Add("ss", "1");
            }
            parameterList.Add("sig", GenerateSignature(parameterList));


            // Build the query
            foreach (var kvp in parameterList)
            {
                queryBuilder.Append(kvp.Key);
                queryBuilder.Append("=");
 
                queryBuilder.Append(System.Web.HttpUtility.UrlEncode(kvp.Value));
 
                queryBuilder.Append("&");
            }
            queryBuilder.Remove(queryBuilder.Length - 1, 1);
            return queryBuilder.ToString();
        }

        internal static string GetRequestUrl(bool useSSL)
        {
            return useSSL ? Constants.RenRenRESTUrl.Replace("http", "https") : Constants.RenRenRESTUrl;
        }
        #endregion Internal Methods

        #region Protected Methods

        /// <summary>
        /// xml序列化完成的回调
        /// </summary>
        /// <param name="result"></param>
        protected void XmlResultOperationCompleted(IAsyncResult result)
        {
            AsyncResult ar = (AsyncResult)result;
            RenRenCallCompleted<string> callback = (RenRenCallCompleted<string>)ar.AsyncState;
            if (callback != null)
            {
                callback(ar.Result, ar.AsyncExternalState, ar.Exception);
            }
        }

		/// <summary>
		/// 请求完成的回调
		/// </summary>
		/// <param name="result"></param>
		protected void OnRenRenCallCompleted<T>(IAsyncResult result)
		{
			OnRenRenCallCompleted<T, T>(result, null);
		}

		/// <summary>
		/// 如果没有异常反序列化返回对象并且激发回调函数
		/// </summary>
		/// <typeparam name="TObject"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="result"></param>
		/// <param name="propName"></param>
		private static void OnRenRenCallCompleted<TObject, TResult>(IAsyncResult result, string propName)
		{
			AsyncResult ar = (AsyncResult)result;
			RenRenCallCompleted<TResult> callback = (RenRenCallCompleted<TResult>)ar.AsyncState;

			if (callback == null)
			{
				return;
			}

			TResult value = default(TResult);
			RenRenException exception = ar.Exception;

			if (exception == null)
			{
				try
				{
					// XML Serialization
					TObject response = Utilities.DeserializeXML<TObject>(ar.Result);

					// JSON Serialization
					//string resultString = FixupJSONString(ar.Result, types[0]);
					//MethodInfo methodInfo = typeof(Utilities).GetMethod("DeserializeJSONObject").MakeGenericMethod(types[0]);
					//resultObject = methodInfo.Invoke(null, new object[] { resultString });

					if (!string.IsNullOrEmpty(propName))
					{
					   value = (TResult)response.GetType().GetProperty(propName).GetValue(response, null);
					}
					else
					{
						value = (TResult)(object)response;
					}
				}
				catch (RenRenException e)
				{
					exception = e;
				}

			}

			callback(value, ar.AsyncExternalState, exception);
		}

        #endregion Protected Methods

        #region Private Methods

        /// <summary>
        /// 修复空数组
        /// </summary>
        /// <param name="result"></param>
        /// <param name="type"></param>
        /// <returns>This method returns a cleaned JSON string.</returns>
        private static string FixupJSONString(string result, Type type)
        {
	            return result;
        }

        /// <summary>
        /// http请求完成的回调
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void OnRequestCompleted(object sender, RequestCompletedEventArgs e)
        {
            RenRenException exception = (RenRenException)e.Error;
            AsyncResult ar = (AsyncResult)e.UserState;
            string response = string.Empty;

            if (exception == null)
            {
                try
                {
                    using (StreamReader streamReader = new StreamReader(e.Response))
                    {
                        response = streamReader.ReadToEnd();
                    }

                    Utilities.ParseException(response, ar.JSONFormat);
                }
                catch (RenRenException ex)
                {
                    exception = ex;
                }
            }

            ar.SetComplete(response, exception);
        }

        /// <summary>
        /// 创建post数据
        /// </summary>
        /// <param name="parameters"></param>
        private string CreatePostData(IDictionary<string, string> parameters)
        {
            parameters.Add("api_key", Session.ApplicationKey);
            parameters.Add("v", Constants.VERSION);
            parameters.Add("call_id", DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture));
            if (Session.SessionSecret != null)
            {
                parameters.Add("ss", "1");
            }
            parameters.Add("sig", GenerateSignature(parameters));


            // Build the query
            var queryBuilder = new StringBuilder();
            foreach (var kvp in parameters)
            {
                queryBuilder.Append(kvp.Key);
                queryBuilder.Append("=");
 
                queryBuilder.Append(System.Web.HttpUtility.UrlEncode(kvp.Value));
 
                queryBuilder.Append("&");
            }
            queryBuilder.Remove(queryBuilder.Length - 1, 1);

            return queryBuilder.ToString();
        }

        private byte[] CreatePostData(IDictionary<string, string> parameters, byte[] data, string contentType, string boundary)
        {
            if (!_mimeTypes.ContainsKey(contentType))
            {
                throw new RenRenException("unsupported content type");
            }
            parameters.Add("api_key", Session.ApplicationKey);
            parameters.Add("session_key", Session.SessionKey);
            parameters.Add("v", Constants.VERSION);
            parameters.Add("call_id", DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture));
            if (Session.SessionSecret != null)
            {
                parameters.Add("ss", "1");
            }
            parameters.Add("sig", GenerateSignature(parameters));


            // Build the query
            var sb = new StringBuilder();
            foreach (var kvp in parameters)
            {
                sb.Append(Constants.PREFIX).Append(boundary).Append(Constants.NEWLINE);
                sb.Append("Content-Disposition: form-data; name=\"").Append(kvp.Key).Append("\"");
                sb.Append(Constants.NEWLINE);
                sb.Append(Constants.NEWLINE);
                sb.Append(kvp.Value);
                sb.Append(Constants.NEWLINE);
            }

            sb.Append(Constants.PREFIX).Append(boundary).Append(Constants.NEWLINE);
            sb.Append("Content-Disposition: form-data; filename=\"dummyFileName." + _mimeTypes[contentType].ToString() + "\"").Append(Constants.NEWLINE);
            sb.Append("Content-Type: ").Append(contentType).Append(Constants.NEWLINE).Append(Constants.NEWLINE);

            byte[] boundaryBytes = Encoding.UTF8.GetBytes(String.Concat(Constants.NEWLINE, Constants.PREFIX, boundary, Constants.PREFIX, Constants.NEWLINE));

            byte[] postHeaderBytes = Encoding.UTF8.GetBytes(sb.ToString());

            using (MemoryStream stream = new MemoryStream(postHeaderBytes.Length + data.Length + boundaryBytes.Length))
            {
                stream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
                stream.Write(data, 0, data.Length);
                stream.Write(boundaryBytes, 0, boundaryBytes.Length);
                return stream.GetBuffer();
            }
        }

        private string SendRequestSynchronous(IDictionary<string, string> parameterDictionary, bool useSession)
        {
            if (useSession)
            {
                parameterDictionary.Add("session_key", Session.SessionKey);
            }

            string requestUrl = GetRequestUrl(parameterDictionary["method"] == "renren.auth.getSession");
         
            string parameters = CreateHTTPParameterList(parameterDictionary);
            if (Batch.IsActive)
            {
                Batch.CallList.Add(parameters);
                return null;
            }
            string result = null;
            if (Session.CompressHttp)
            {
                result = processCompressedResponse(postRequest(requestUrl, parameters, true));
            }
            else
            {
                result = processResponse(postRequest(requestUrl, parameters, false));
            }

            return string.IsNullOrEmpty(result) ? null : result;
        }
        
        #endregion Private Methods

        #endregion Methods
    }

    #region Delegates
    public delegate void RenRenCallCompleted<T>(T result, Object state, RenRenException e);
    public delegate void RenRenCallCompleted(AsyncCallback callback, Object state, Object externalstate);

    #endregion Delegates
}
